package require Iexpect

# Differences with Expect:
#  - -file is interpreted as a request for a map description file (in DPjudge map format),
#    automatically embedding external files required by this description file.
#  - Ignores white space by default (i.e. -white is default).

itcl::class MapExpect {
	inherit iexpect::Expect

	constructor {args} {
		chain
	} {
		::set iWhite 1
		alter $args
	}

	method alter {optionList} {
		if {[llength $optionList] >= 2} {
			if [string equal [lindex $optionList 0] "-file"] {
				alterInput [itcl::code [MapDescInput #auto [lindex $optionList 1]]]
				alter [lrange $optionList 2 end]
				return
			}
		}
		chain $optionList
	}
}

itcl::class MapDescInput {
	inherit iexpect::FileInput

	constructor {descFile} {
		chain $descFile
	} {
		::set iDirectory [file dirname $descFile]
		if {[string first "_" [file rootname $descFile]] != -1} {
			set encoding "iso8859-1"
		}
	}

	destructor {
		if {[string compare $iSubMap {}] != 0} {itcl::delete object $iSubMap}
	}

	method read {chunkSize} {
		::set data $iBuffer
		if {[::set remainder [expr $chunkSize - [string length $data]]] < 0} {
			::set iBuffer [string range $iBuffer $chunkSize end]]
			return [string range $data 0 [expr $chunkSize - 1]]
		}
		::set iBuffer {}

		if {[string compare $iSubMap {}] != 0} {
			append data [$iSubMap read $remainder]
			if {![$iSubMap atEnd]} {return $data}
			itcl::delete object $iSubMap; ::set iSubMap {}; ::set iClosed $iCachedClosed
			# Continue reading from this file.
		}

		while {!$iClosed && ([::set remainder [expr $chunkSize - [string length $data]]] < 0)} {
			# Always read line per line so that we don't miss a sub file.
			::set line [iexpect::FileInput::readLine]
			if [regexp -nocase "^use\[ \t\]+(.+)$" [string trim $line] dummy subFileSpec] {
				::set iSubMap [MapDescInput #auto "$iDirectory/$subFileSpec"]; ::set iCachedClosed $iClosed; ::set iClosed 0
				append data [$iSubMap read $remainder]
				if {![$iSubMap atEnd]} {return $data}
				itcl::delete object $iSubMap; ::set iSubMap {}; ::set iClosed $iCachedClosed
				# Continue reading from this file.
				continue
			}
			append data "$line\n"
		}
		
		::set iBuffer [string range $data $chunkSize end]
		string range $data 0 [expr $chunkSize - 1]	
	}

	method readLine {} {
		# Since we're always reading complete lines, we know that if there's something in the buffer we don't need to look further.
		if {[string compare $iBuffer {}] != 0} {
			return [chain]
		}

		if {[string compare $iSubMap {}] != 0} {
			::set line [$iSubMap readLine]
			if [$iSubMap atEnd] {itcl::delete object $iSubMap; ::set iSubMap {}; ::set iClosed $iCachedClosed}
			return $line
		}

		if {$iClosed} {return {}}
		
		::set line [chain]
		if [regexp -nocase "^(use|map)\[ \t\]+(.+)$" [string trim $line] dummy keyword subFileSpec] {
			if {![string length [file extension $subFileSpec]]} {append subFileSpec ".map"}
			::set iSubMap [MapDescInput #auto "$iDirectory/$subFileSpec"]; ::set iCachedClosed $iClosed; ::set iClosed 0
			::set line [$iSubMap readLine]
			if [$iSubMap atEnd] {itcl::delete object $iSubMap; ::set iSubMap {}; ::set iClosed $iCachedClosed}
		}
		return $line
	}

	variable iDirectory
	variable iSubMap {}
	variable iCachedClosed
}

package provide MapExpect 1.0
